﻿Namespace Commanding
    Public Class VisitCollectionCommand
        Inherits AsyncCommand

        Public Shared ReadOnly ItemActionProperty As DependencyProperty = DependencyProperty.Register("ItemAction", GetType(Action(Of Object)), GetType(VisitCollectionCommand), New PropertyMetadata(AddressOf OnItemVisitorChanged))
        Public Property ItemAction As Action(Of Object)
            Get
                Return GetValue(ItemActionProperty)
            End Get
            Set(ByVal value As Action(Of Object))
                SetValue(ItemActionProperty, value)
            End Set
        End Property

        Public Shared ReadOnly ItemMethodProperty As DependencyProperty = DependencyProperty.Register("ItemMethod", GetType(String), GetType(VisitCollectionCommand), New PropertyMetadata(AddressOf OnItemVisitorChanged))
        Public Property ItemMethod As String
            Get
                Return GetValue(ItemMethodProperty)
            End Get
            Set(ByVal value As String)
                SetValue(ItemMethodProperty, value)
            End Set
        End Property
        Private Shared Sub OnItemVisitorChanged(ByVal d As VisitCollectionCommand, ByVal e As DependencyPropertyChangedEventArgs)
            If d.ItemAction IsNot Nothing AndAlso Not String.IsNullOrEmpty(d.ItemMethod) Then
                Throw New ArgumentException("Cannot set ItemMethod and ItemAction together.")
            End If
        End Sub

        Public Shared ReadOnly CanExecuteFuncProperty As DependencyProperty = DependencyProperty.Register("CanExecuteFunc", GetType(Func(Of Boolean)), GetType(VisitCollectionCommand))
        Public Property CanExecuteFunc As Func(Of Boolean)
            Get
                Return GetValue(CanExecuteFuncProperty)
            End Get
            Set(ByVal value As Func(Of Boolean))
                SetValue(CanExecuteFuncProperty, value)
            End Set
        End Property

        Protected Overrides ReadOnly Property SupportsCanExecute As Boolean
            Get
                Return True
            End Get
        End Property

        Public Overrides Function CanExecute(ByVal sender As Object, ByVal parameter As Object) As Boolean
            Dim result As Boolean = False
            If ((ItemAction IsNot Nothing OrElse Not String.IsNullOrEmpty(ItemMethod)) AndAlso parameter IsNot Nothing AndAlso TypeOf parameter Is IList AndAlso DirectCast(parameter, IList).Count > 0) Then
                If CanExecuteFunc Is Nothing Then
                    result = True
                Else
                    Try
                        result = CanExecuteFunc.Invoke()
                    Catch ex As Exception
                    End Try
                End If
            End If
            Return result
        End Function

        Private _List As IList

        Protected Overrides Sub OnExecuted(sender As Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
            _List = e.Parameter
            MyBase.OnExecuted(sender, e)
        End Sub

        Protected Overrides Sub OnProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs)
            UserState = e.UserState
            ProgressPercent = 100 * e.ProgressPercentage / _List.Count
            Dim i As Integer = e.ProgressPercentage
            If i >= 0 Then
                _List.Item(i) = e.UserState
            End If
        End Sub

        Protected Overrides Sub OnCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs)
            MyBase.OnCompleted(sender, e)
            _List = Nothing
        End Sub

        Protected Overrides Function CreateInstanceCore() As System.Windows.Freezable
            Return New VisitCollectionCommand
        End Function

        Protected Overrides Function GetDoWorkHandler() As System.ComponentModel.DoWorkEventHandler
            Dim visitor As New ItemVisitor(ItemMethod, ItemAction)
            Return AddressOf visitor.DoWork
        End Function

        Private Class ItemVisitor
            Private _ItemMethod As String
            Private _ItemAction As Action(Of Object)

            Public Sub New(ByVal itemMethod As String, ByVal itemAction As Action(Of Object))
                _ItemMethod = itemMethod
                _ItemAction = itemAction
            End Sub


            Public Sub DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
                Dim list As IList = e.Argument
                Dim worker As System.ComponentModel.BackgroundWorker = sender
                Dim n As Integer = list.Count - 1

                For i As Integer = 0 To n
                    Dim item As Object = list.Item(i)

                    If _ItemAction IsNot Nothing Then
                        If TypeOf item Is System.Windows.Threading.DispatcherObject Then
                            DirectCast(item, System.Windows.Threading.DispatcherObject).Dispatcher.Invoke(_ItemAction, Windows.Threading.DispatcherPriority.Background, item)
                        Else
                            _ItemAction.Invoke(item)
                        End If
                    ElseIf Not String.IsNullOrEmpty(_ItemMethod) Then
                        Dim a As Action = Action.CreateDelegate(GetType(Action), item, _ItemMethod)
                        If TypeOf item Is System.Windows.Threading.DispatcherObject Then
                            DirectCast(item, System.Windows.Threading.DispatcherObject).Dispatcher.Invoke(a, Windows.Threading.DispatcherPriority.Background)
                        Else
                            a.Invoke()
                        End If
                    End If
                    If worker.CancellationPending Then
                        e.Cancel = True
                        Exit For
                    Else
                        Dim percent As Integer = (100 * i) / (n + 1)
                        worker.ReportProgress(i, item)
                    End If

                Next
                If Not e.Cancel Then
                    e.Result = list
                End If
            End Sub
        End Class
    End Class

End Namespace
